package cn.gowss.judger.core;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LocalCommandExecutorImpl implements LocalCommandExecutor {

    static final Logger logger = LoggerFactory.getLogger(LocalCommandExecutorImpl.class);
	
	private static int THERAD_SLEEP_TIME = 500;

    static ExecutorService pool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3L, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>());

	public Map<String, Object> executeCommand(String command, String inputFilePath, String outputFilePath, int timeLimit, int memeryLimit) {
        long timeMillis = System.currentTimeMillis();
    	Process process = null;
        OutputStream pOut = null;
        InputStream pIn = null;
        StreamGobbler outputGobbler = null;
        Future<Integer> executeFuture = null;
        try {
            logger.info(command.toString());
            process = Runtime.getRuntime().exec(command);
            final Process p = process;

            // close process's output stream.
            if(inputFilePath != null && inputFilePath != "") {
            	pOut = p.getOutputStream();
            	File file = new File(inputFilePath);
    			FileUtils.copyFile(file, pOut);
    			pOut.close();
            }else {
            	p.getOutputStream().close();
            }

            pIn = p.getInputStream();
            if(command.contains("java")) {
            	outputGobbler = new StreamGobbler(pIn, "OUTPUT", outputFilePath);
            	outputGobbler.start();
//            pErr = p.getErrorStream();
//            errorGobbler = new StreamGobbler(pErr, "ERROR", outputFilePath);
//            errorGobbler.start();
            	
            	// create a Callable for the command's Process which can be called by an Executor
            	Callable<Integer> call = new Callable<Integer>() {
            		public Integer call() throws Exception {
            			p.waitFor();
            			return p.exitValue();
            		}
            	};
            	
            	// submit the command's call and get the result from a
            	executeFuture = pool.submit(call);
            	Map<String, Object> m = new HashMap<String, Object>(3);
            	int exitCode = executeFuture.get(timeLimit, TimeUnit.MILLISECONDS);
            	long usedTime = System.currentTimeMillis() - timeMillis;
            	m.put("exitCode", Integer.valueOf(exitCode));
            	m.put("usedTime", Long.valueOf(usedTime).intValue());
            	m.put("usedMemory", 0);
            	return m;
            }
            
          Thread.sleep(THERAD_SLEEP_TIME);
          File writeName = new File(outputFilePath); // 相对路径，如果没有则要建立一个新的output.txt文件
          writeName.createNewFile();// 创建新文件,有同名的文件的话直接覆盖
          int count = 0;
          count = pIn.available();
          if(count > 0) {
          	byte[] buffer = new byte[count];
          	IOUtils.read(pIn, buffer);
//          	FileUtils.copyInputStreamToFile(pIn, writeName);
          	FileUtils.writeByteArrayToFile(writeName, buffer);
          }

            boolean waitFor = p.waitFor(timeLimit+THERAD_SLEEP_TIME, TimeUnit.MILLISECONDS);
            Map<String, Object> m = new HashMap<String, Object>(3);
            if(waitFor) {
            	int exitCode = p.exitValue();
            	long usedTime = System.currentTimeMillis() - timeMillis - THERAD_SLEEP_TIME;
            	m.put("exitCode", Integer.valueOf(exitCode));
            	m.put("usedTime", Long.valueOf(usedTime).intValue());
            	m.put("usedMemory", 0);
            	return m;
            }else {
            	p.destroyForcibly();
            	long usedTime = System.currentTimeMillis() - timeMillis - THERAD_SLEEP_TIME;
            	m.put("exitCode", Integer.valueOf(-1));
            	m.put("usedTime", Long.valueOf(usedTime).intValue());
            	m.put("usedMemory", 0);
            	return m;
            }
//            return new ExecuteResult(exitCode, outputGobbler.getContent());

        } catch (IOException ex) {
            String errorMessage = "The command [" + command + "] execute failed.";
            logger.error(errorMessage, ex);
            Map<String, Object> m = new HashMap<String, Object>(3);
            m.put("exitCode", Integer.valueOf(-1));
            m.put("usedTime", 0);
        	m.put("usedMemory", 0);
            return m;
        } catch (TimeoutException ex) {
            String errorMessage = "The command [" + command + "] timed out.";
            logger.error(errorMessage, ex);
            Map<String, Object> m = new HashMap<String, Object>(3);
            m.put("exitCode", Integer.valueOf(-1));
            m.put("usedTime", 0);
        	m.put("usedMemory", 0);
            return m;
        } catch (ExecutionException ex) {
            String errorMessage = "The command [" + command + "] did not complete due to an execution error.";
            logger.error(errorMessage, ex);
            Map<String, Object> m = new HashMap<String, Object>(3);
            m.put("exitCode", Integer.valueOf(-1));
            m.put("usedTime", 0);
        	m.put("usedMemory", 0);
            return m;
        } catch (InterruptedException ex) {
            String errorMessage = "The command [" + command + "] did not complete due to an interrupted error.";
            logger.error(errorMessage, ex);
            Map<String, Object> m = new HashMap<String, Object>(3);
            m.put("exitCode", Integer.valueOf(-1));
            m.put("usedTime", 0);
        	m.put("usedMemory", 0);
            return m;
        } finally {
            if (executeFuture != null) {
                try {
                    executeFuture.cancel(true);
                } catch (Exception ignore) {
                    ignore.printStackTrace();
                }
            }
            if (outputGobbler != null && !outputGobbler.isInterrupted()) {
            	outputGobbler.interrupt();
            }
            if (pIn != null) {
            	this.closeQuietly(pIn);
            }
            if (pOut != null) {
                this.closeQuietly(pOut);
            }
            if (process != null) {
                process.destroy();
            }
        }
    }

    private void closeQuietly(Closeable c) {
        try {
            if (c != null) {
                c.close();
            }
        } catch (IOException e) {
            logger.error("exception", e);
        }
    }
    
}